#include "stdafx.h"
#include "MysqlAdapter.h"
#include <sstream>
#include "RecordException.h"
#include "RecordBase.h"
#include "MysqlTypeUtil.h"
#include "MysqlInitInfo.h"
#include "Any.h"
#include <boost/lexical_cast.hpp>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace serika
{
	namespace db
	{
		MysqlAdapter::MysqlAdapter(const MysqlInitInfo& initInfo)
			: Adapter(), mPool(initInfo.concurrency), mTransactionConnection(0)
		{
		}
	
		MysqlAdapter::~MysqlAdapter()
		{
		}

		// static
		MysqlAdapter* MysqlAdapter::NewAdapter(const Any& initInfo)
		{
			const MysqlInitInfo& info = initInfo.Get<MysqlInitInfo>();
			return new MysqlAdapter(info);
		}
	
		bool MysqlAdapter::Create(const Any& initInfo)
		{
			//Base::Create(initInfo);
			return mPool.Create(initInfo);
		}
	
		void MysqlAdapter::Destroy()
		{
			mPool.Close();
			mysql_library_end();
			delete this;
		}
	
		int MysqlAdapter::ReadRecords(std::vector<FieldMap*>& resultSet, 
			const std::string& tableName,
			const std::string& conditions, 
			int offset,
			int limit,
			const std::string& select, 
			const std::string& joins, 
			const std::string& order, 
			const std::string& group,
			const std::string& having)
		{
	//		ProFunc();
			resultSet.clear();
			MysqlConnection* conn = this->GetConnection();
			if (!conn)
			{
				return 0;
			}
			MYSQL* mysql = conn->GetRawConnection();
			
			std::string statement = this->ConstructSelect(tableName, conditions, offset, limit, select, joins, order, group, having);
	
			{
	//			Profile("mysql_real_query");
				int ret = mysql_real_query(mysql, statement.c_str(), statement.length());
				if (ret != 0)
				{
					this->ReleaseConnection(conn);
					this->ThrowRecordException(ret, mysql_error(mysql));
				}
			}
	
			MYSQL_RES* res = mysql_store_result(mysql);
			if (res == 0)
			{
				this->ReleaseConnection(conn);
				return 0;
			}
	
			{
	//			Profile("store result");
				const unsigned int fieldCount = mysql_field_count(mysql);
				MYSQL_ROW row = mysql_fetch_row(res);
				while (row != 0)
				{
					FieldMap* fm = new FieldMap;
					unsigned long* lengths = mysql_fetch_lengths(res);
					for (unsigned int i = 0; i < fieldCount; ++i)
					{
						mysql_field_seek(res, i);
						MYSQL_FIELD* field = mysql_fetch_field(res);
						MysqlTypeUtil::InsertValue(fm, field, row[i], lengths[i]);
					}
					resultSet.push_back(fm);
					row = mysql_fetch_row(res);
				}
			}
	
			mysql_free_result(res);
			this->ReleaseConnection(conn);
			return static_cast<int>(resultSet.size());
		}
	
		int MysqlAdapter::Count(const std::string& tableName, const std::string& column, const std::string& conditions, const std::string& joins)
		{
			MysqlConnection* conn = this->GetConnection();
			if (!conn)
			{
				return 0;
			}
			MYSQL* mysql = conn->GetRawConnection();
	
			std::string select;
			if (column == "*")
			{
				select = "COUNT(*) AS `count_all`";
			}
			else
			{
				std::stringstream ss;
				ss << "COUNT(`" << column << "`) AS `count_all`";
				select = ss.str();
			}
			std::string statement = this->ConstructSelect(tableName, conditions, 0, 0, select, joins, "", "", "");
			
			int ret = 0;
			{
	//			Profile("mysql_real_query");
				ret = mysql_real_query(mysql, statement.c_str(), statement.length());
				if (ret != 0)
				{
					this->ReleaseConnection(conn);
					this->ThrowRecordException(ret, mysql_error(mysql));
				}
			}
	
			MYSQL_RES* res = mysql_store_result(mysql);
			if (res == 0)
			{
				this->ReleaseConnection(conn);
				return 0;
			}
	
			MYSQL_ROW row = mysql_fetch_row(res);
			if (row != 0)
			{
				ret = boost::lexical_cast<int>(row[0]);
			}
	
			mysql_free_result(res);
			this->ReleaseConnection(conn);
			return ret;
		}
	
		bool MysqlAdapter::CreateRecord(FieldMap* fields, const std::string& tableName, const std::string& primaryKey)
		{
	//		ProFunc();
			MysqlConnection* conn = this->GetConnection();
			if (!conn)
			{
				return false;
			}
			MYSQL* mysql = conn->GetRawConnection();
	
			std::string statement = this->ConstructInsert(fields, tableName);
			int ret = mysql_real_query(mysql, statement.c_str(), statement.length());
			if (ret != 0)
			{
				this->ReleaseConnection(conn);
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
			std::stringstream ss;
			ss << "SELECT * FROM `" << tableName << "` WHERE `" << primaryKey << "`='" << mysql_insert_id(mysql) << "'";
			statement = ss.str();
			ret = mysql_real_query(mysql, statement.c_str(), statement.length());
			if (ret != 0)
			{
				this->ReleaseConnection(conn);
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
			MYSQL_RES* res = mysql_store_result(mysql);
			if (res == 0)
			{
				this->ReleaseConnection(conn);
				return false;
			}
			const unsigned int fieldCount = mysql_field_count(mysql);
			MYSQL_ROW row = mysql_fetch_row(res);
			if (row != 0)
			{
				fields->clear();
				unsigned long* lengths = mysql_fetch_lengths(res);
				for (unsigned int i = 0; i < fieldCount; ++i)
				{
					mysql_field_seek(res, i);
					MYSQL_FIELD* field = mysql_fetch_field(res);
					MysqlTypeUtil::InsertValue(fields, field, row[i], lengths[i]);
				};
			}
			mysql_free_result(res);
			this->ReleaseConnection(conn);
			return true;
		}
	
		bool MysqlAdapter::CreateRecords(std::vector<FieldMap*>& fieldMaps, const std::string& tableName, const std::string& primaryKey)
		{
			MysqlConnection* conn = this->GetConnection();
			if (!conn)
			{
				return false;
			}
			MYSQL* mysql = conn->GetRawConnection();
	
			std::string statement = this->ConstructMultipleInsert(fieldMaps, tableName);
			int ret = mysql_real_query(mysql, statement.c_str(), statement.length());
			if (ret != 0)
			{
				this->ReleaseConnection(conn);
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
			my_ulonglong affectedRows = mysql_affected_rows(mysql);
	
			std::stringstream ss;
			ss << "SELECT * FROM `" << tableName << "` WHERE `" << primaryKey << "`>='" << mysql_insert_id(mysql) << "' LIMIT " << affectedRows;
			statement = ss.str();
			ret = mysql_real_query(mysql, statement.c_str(), statement.length());
			if (ret != 0)
			{
				this->ReleaseConnection(conn);
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
			MYSQL_RES* res = mysql_store_result(mysql);
			if (res == 0)
			{
				this->ReleaseConnection(conn);
				return false;
			}
			const unsigned int fieldCount = mysql_field_count(mysql);
			MYSQL_ROW row = mysql_fetch_row(res);
			std::vector<FieldMap*>::iterator itr = fieldMaps.begin();
			while (row != 0)
			{
				FieldMap* fields = *itr;
				fields->clear();
				unsigned long* lengths = mysql_fetch_lengths(res);
				for (unsigned int i = 0; i < fieldCount; ++i)
				{
					mysql_field_seek(res, i);
					MYSQL_FIELD* field = mysql_fetch_field(res);
					MysqlTypeUtil::InsertValue(fields, field, row[i], lengths[i]);
				};
	
				++itr;
				row = mysql_fetch_row(res);
			}
			mysql_free_result(res);
			this->ReleaseConnection(conn);
			return true;
		}
	
		int MysqlAdapter::UpdateAllRecords(const std::string& settings, const std::string& conditions, const std::string& tableName)
		{
			MysqlConnection* conn = this->GetConnection();
			if (!conn)
			{
				return 0;
			}
			MYSQL* mysql = conn->GetRawConnection();
	
			std::string statement = this->ConstructUpdate(tableName, settings, conditions);
	
			int ret = mysql_real_query(mysql, statement.c_str(), statement.length());
			if (ret != 0)
			{
				this->ReleaseConnection(conn);
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
	
			ret = static_cast<int>(mysql_affected_rows(mysql));
	
			this->ReleaseConnection(conn);
			return ret;
		}
	
		int MysqlAdapter::DeleteRecord(const std::string& conditions, const std::string& tableName)
		{
			MysqlConnection* conn = this->GetConnection();
			if (!conn)
			{
				return 0;
			}
			MYSQL* mysql = conn->GetRawConnection();
	
			std::string statement = this->ConstructDelete(tableName, conditions);
	
			int ret = mysql_real_query(mysql, statement.c_str(), statement.length());
			if (ret != 0)
			{
				this->ReleaseConnection(conn);
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
			ret = static_cast<int>(mysql_affected_rows(mysql));
	
			this->ReleaseConnection(conn);
			return ret;
		}
	
		void MysqlAdapter::BeginTransaction()
		{
			MysqlConnection* conn = this->GetTransactionConnection();
			if (!conn)
			{
				return;
			}
			MYSQL* mysql = conn->GetRawConnection();
	
			int ret = mysql_real_query(mysql, "BEGIN", 5);
			if (ret != 0)
			{
				this->ReleaseTransactionConnection();
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
		}
	
		void MysqlAdapter::CommitTransaction()
		{
			MysqlConnection* conn = this->GetConnection();
			if (!conn)
			{
				return;
			}
			MYSQL* mysql = conn->GetRawConnection();
	
			int ret = mysql_real_query(mysql, "COMMIT", 6);
			if (ret != 0)
			{
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
			this->ReleaseTransactionConnection();
		}
	
		void MysqlAdapter::RollbackTransaction()
		{
			MysqlConnection* conn = this->GetConnection();
			if (!conn)
			{
				return;
			}
			MYSQL* mysql = conn->GetRawConnection();
	
			int ret = mysql_real_query(mysql, "ROLLBACK", 8);
			if (ret != 0)
			{
				this->ThrowRecordException(ret, mysql_error(mysql));
			}
			this->ReleaseTransactionConnection();
		}
	
		std::string MysqlAdapter::EscapeString(const std::string& text)
		{
			const size_t DEFAULT_BUF_LEN = 1024;
			char buf[DEFAULT_BUF_LEN] = {0};
			const size_t len = text.length() * 2 + 1;
			char* ret = len > DEFAULT_BUF_LEN ? new char[len] : buf;
			int r = mysql_escape_string(ret, text.c_str(), text.size());
	
			std::string t = ret;
	
			if (len > DEFAULT_BUF_LEN)
			{
				delete[] ret;
			}
	
			return t;
		}
	
		inline void MysqlAdapter::ThrowRecordException(int code, const char* message)
		{
			ut::Sstream ss;
			ss << "Mysql Error: " << message;
			throw RecordException(code, ss.str().c_str());
		}
	
		inline MysqlConnection* MysqlAdapter::GetConnection()
		{
			//ProFunc();
			threading::RecursiveMutex::Guard guard(mTransactionMutex);
			if (mTransactionConnection != 0)
			{
				return mTransactionConnection;
			}
			else
			{
				return mPool.GetConnection();
			}
		}
	
		inline void MysqlAdapter::ReleaseConnection(MysqlConnection* conn)
		{
			if (mTransactionConnection != conn)
			{
				mPool.ReleaseConnection(conn);
			}
		}
	
		inline MysqlConnection* MysqlAdapter::GetTransactionConnection()
		{
			mTransactionMutex.Acquire();
			if (mTransactionConnection != 0)
			{
				return mTransactionConnection;
			}
			mTransactionConnection = mPool.GetConnection();
			return mTransactionConnection;
		}
	
		inline void MysqlAdapter::ReleaseTransactionConnection()
		{
			if (mTransactionConnection != 0)
			{
				mPool.ReleaseConnection(mTransactionConnection);
				mTransactionConnection = 0;
			}
			mTransactionMutex.Release();
		}
	}
}
